/***************************************************************************/
/**

   @file         main.c

   @author       Stephen Brennan, Jiasheng Hao

   @date         Sunday,  19 September 2021

   @brief        HSH (Hao's SHell)

*******************************************************************************/

#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

/*
  Function Declarations for builtin shell commands:
*/

int hsh_cd(char *argv[]);
int hsh_help(char *argv[]);
int hsh_exit(char *argv[]);

/* 
   Definitions of builtin functions 
*/

typedef struct builtins_t 
{
    char *name;
    int (*func) (char *[]);
    char *help;
} BUILTIN;

/*
  List of builtin commands, followed by their corresponding functions.
*/

BUILTIN builtins[] = {
    { "cd",   hsh_cd,  "Demo cd funcs" },
    { "help", hsh_help, "Demo help function"},
    { "exit", hsh_exit, "Demo exit function"},
    { NULL,   NULL,     NULL },
};


int hsh_num_builtins()
{
    return sizeof(builtins)/ sizeof(struct builtins_t) - 1;
}

/*
  Builtin function implementations.
*/

/**
   @brief Bultin command: change cdectory.
   @param argv List of args.  argv[0] is "cd".  argv[1] is the cdectory.
   @return Always returns 1, to continue executing.
*/
int hsh_cd(char *argv[])
{
    if (argv[1] == NULL) {
        fprintf(stderr, "hsh: expected argument to \"cd\"\n");
    } else {
        if (chdir(argv[1]) != 0) {
            perror("hsh");
        }
    }
    return 1;
}

/**
   @brief Builtin command: print help.
   @param argv List of args.  Not examined.
   @return Always returns 1, to continue executing.
*/
int hsh_help(char *argv[])
{
    int i;
    
    printf("Stephen Brennan's HSH\n");
    printf("Type program names and arguments, and hit enter.\n");
    printf("The following are built in:\n");

    for (i = 0; i < hsh_num_builtins(); i++) {
        if ((argv[1] == NULL) || (strcmp(argv[1], builtins[i].name) == 0)) {
            printf("  %s\n", builtins[i].help);
        }
    }

    printf("Use the man command for information on other programs.\n");
    return 1;
}

/**
   @brief Builtin command: exit.
   @param argv List of args.  Not examined.
   @return Always returns 0, to terminate execution.
*/
int hsh_exit(char *argv[])
{
    printf("See you soon!\n");
    
    return 0;
}

/**
   @brief Launch a program and wait for it to terminate.
   @param argv Null terminated list of arguments (including program).
   @return Always returns 1, to continue execution.
*/
int hsh_launch(char *argv[])
{
    pid_t pid;
    int   status;

    pid = fork();
    
    if (pid < 0) {                   // Error forking
        perror("hsh");
    }
    else if (pid == 0) {             // Child process
        if (execvp(argv[0], argv) == -1) {
            perror("hsh");
        }
        exit(EXIT_FAILURE);
    }
    else {                           // Parent process
        do {
            waitpid(pid, &status, WUNTRACED);
        } while (!WIFEXITED(status) && !WIFSIGNALED(status));
    }

    return 1;
}

/**
   @brief Execute shell built-in or launch program.
   @param argv Null terminated list of arguments.
   @return 1 if the shell should continue running, 0 if it should terminate
*/
int hsh_execute(char *argv[])
{
    int i;

    if (argv[0] == NULL) {
        // An empty command was entered.
        return 1;
    }

    for (i = 0; i < hsh_num_builtins(); i++) {
        if (strcmp(argv[0], builtins[i].name) == 0) {
            return (builtins[i].func)(argv);
        }
    }

    return hsh_launch(argv);
}

/**
   @brief Read a line of input from stdin.
   @return The line from stdin.
*/
char *hsh_read_line(void)
{
    char *line = NULL;
    size_t bufsize = 0; // have getline allocate a buffer for us
    if (getline(&line, &bufsize, stdin) == -1) {
        if (feof(stdin)) {
            exit(EXIT_SUCCESS);  // We received an EOF
        } else  {
            perror("hsh: getline\n");
            exit(EXIT_FAILURE);
        }
    }
    return line;
}

#define HSH_TOK_BUFSIZE 64
#define HSH_TOK_DELIM " \t\r\n\a"
/**
   @brief Split a line into tokens (very naively).
   @param line The line.
   @return Null-terminated array of tokens.
*/
char **hsh_split_line(char *line)
{
    int bufsize = HSH_TOK_BUFSIZE, position = 0;
    char **tokens = malloc(bufsize * sizeof(char*));
    char *token, **tokens_backup;

    if (!tokens) {
        fprintf(stderr, "hsh: allocation error\n");
        exit(EXIT_FAILURE);
    }

    token = strtok(line, HSH_TOK_DELIM);
    while (token != NULL) {
        tokens[position] = token;
        position++;

        if (position >= bufsize) {
            bufsize += HSH_TOK_BUFSIZE;
            tokens_backup = tokens;
            tokens = realloc(tokens, bufsize * sizeof(char*));
            if (!tokens) {
                free(tokens_backup);
                fprintf(stderr, "hsh: allocation error\n");
                exit(EXIT_FAILURE);
            }
        }

        token = strtok(NULL, HSH_TOK_DELIM);
    }
    tokens[position] = NULL;
    return tokens;
}

/**
   @brief Loop getting input and executing it.
*/
void hsh_loop(void)
{
    char *line;
    char **argv;
    int status;

    do {
        printf("> ");
        line = hsh_read_line();
        argv = hsh_split_line(line);
        status = hsh_execute(argv);

        free(line);
        free(argv);
    } while (status);
}

/**
   @brief Main entry point.
   @param argc Argument count.
   @param argv Argument vector.
   @return status code
*/
int main(int argc, char *argv[])
{
    // Load config files, if any.

    // Run command loop.
    hsh_loop();

    // Perform any shutdown/cleanup.

    return EXIT_SUCCESS;
}

